package procd

import (
	"mediumkube/pkg/utils"
	"sync"
	"time"
)

type Node struct {
	task     Task  // Task
	ddl      int64 // When is the task avaken millis
	interval int64 // Interval initially set to node, used to add node back to list
	next     *Node // Points to next node
}

func NewNode(t Task, interval int64) *Node {
	return &Node{
		task:     t,
		ddl:      interval + time.Now().UnixMilli(),
		interval: interval,
	}
}

type TaskList struct {
	head   *Node
	cond   sync.Cond
	active bool
}

func NewTaskList() *TaskList {
	mutex := sync.Mutex{}
	return &TaskList{
		head: nil,
		cond: *sync.NewCond(&mutex),
	}
}

// find two nodes in the linked list where:
// first node has smaller interval than given one
// second node has large interval than given one
// nil, non-nil if the head of the list is greater than given one
// non-nil, nil if all values are less than given one
func (tl *TaskList) findPivot(key int64) (*Node, *Node) {
	current := tl.head
	if current != nil && current.ddl > key {
		return nil, current
	}
	for {
		if current == nil {
			return current, nil
		}
		if current.next == nil {
			return current, nil
		}
		if current.next.ddl > key {
			return current, current.next
		}
		current = current.next
	}
}

func (tl *TaskList) Schedule(t Task, interval int64) {
	node := NewNode(t, interval)
	tl.insertNode(node)
}

func (tl *TaskList) ScheduleFunc(f func(), interval int64) {
	tl.Schedule(&SimpleTask{f: f}, interval)
}

func (tl *TaskList) insertNode(n *Node) {
	prev, nxt := tl.findPivot(n.ddl)

	if prev == nil {
		// The node is inserted as head
		n.next = nxt
		tl.head = n
	} else {
		prev.next = n
		n.next = nxt
	}
	tl.cond.Signal()
}

func (tl *TaskList) forEach(f func(*Node)) {
	current := tl.head
	for {
		if current == nil {
			return
		}
		f(current)
		current = current.next
	}
}

func (tl *TaskList) RunNext() {
	if tl.head == nil {
		tl.cond.L.Lock()
		tl.cond.Wait()
		return
	}
	now := time.Now().UnixMilli()
	head := tl.head
	time.Sleep(time.Duration(utils.Max(head.ddl-now, 0)) * time.Millisecond)
	// Decrease interval

	head.task.Run()
	tl.head = tl.head.next
	head.ddl = time.Now().UnixMilli() + head.interval
	tl.insertNode(head)
}

func (tl *TaskList) Start() {
	tl.active = true
	for {
		if !tl.active {
			break
		}
		tl.RunNext()
	}
}

func (tl *TaskList) Stop() {
	tl.active = false
}
